﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading;
using System.Diagnostics;

namespace Dolly.Tests
{
	/// <summary>
	/// Summary description for UnitTest1
	/// </summary>
	[TestClass]
	public class DollyTester
	{

		/// <summary>
		/// Clones enumerable and checks count
		/// </summary>
		[TestMethod]
		public void Test2Threads()
		{

			int length = 1234;
			var ie = Enumerable.Range(1234, length).Select(i => i.ToString());

			ICloner<string> cloner = new Cloner<string>(ie);

			var cloneA = cloner.GetClone();
			var cloneB = cloner.GetClone();

			cloner.AllowReading();

			var alist = new List<string>();
			var blist = new List<string>();

			Thread tA = new Thread(() => { foreach (var a in cloneA) alist.Add(a); });
			Thread tB = new Thread(() => { foreach (var b in cloneB) blist.Add(b); });

			tA.Start();
			tB.Start();

			do { Thread.Sleep(50); } while (tA.IsAlive && tB.IsAlive);

			Assert.AreEqual(length, alist.Count);
			Assert.AreEqual(length, blist.Count);
		}

		/// <summary>
		/// Clones multiple enumerables and checks Sum() method
		/// </summary>
		[TestMethod]
		public void TestLotsThreads()
		{
			Random r = new Random();
			int length = 12345;
			int count = 64;

			var ie = Enumerable.Range(1, length);
			var expectedSum = (length * (1 + length)) / 2;

			var clones = new Dictionary<string, IEnumerable<int>>();
			var cloneSums = new Dictionary<string, int>();
			Thread[] threads = new Thread[count];
			ICloner<int> cloner = new Cloner<int>(ie);

			for (int i = 0; i < count; i++)
			{
				clones.Add("string" + i, cloner.GetClone());
				cloneSums.Add("string" + i, 0);
			}

			cloner.AllowReading();

			for (int i = 0; i < count; i++)
			{
				threads[i] =
				new Thread((_n) =>
				{
					string key = "string" + _n;
					cloneSums[key] = clones[key].Sum();
				});
				threads[i].Start(i);
			}

			bool b = false;
			do
			{
				Thread.Sleep(50);
				b = threads.Any(t => t.IsAlive);
			} while (b);


			for (int i = 0; i < count; i++)
				Assert.AreEqual(expectedSum, cloneSums["string" + i]);
		}

		[TestMethod]
		public void TestMoreThanLotsOfThreads()
		{
			int length = 1234;
			int baseCount = 64;
			int secondTryCount = 64;
			int shiftBase = 100;
			int victimIdx = 0;
			var someEnumerable = Enumerable.Range(1, length);
			var expectedSum = (length * (1 + length)) / 2;
			Dictionary<int, IEnumerable<int>> clones = new Dictionary<int, IEnumerable<int>>();
			Dictionary<int, int> cloneSums = new Dictionary<int, int>();
			Dictionary<int, Thread> threads = new Dictionary<int, Thread>();
			ICloner<int> cloner = new Cloner<int>(someEnumerable);

			// clone some enumerables
			for (int i = 0; i < baseCount; i++)
			{
				clones.Add(i, cloner.GetClone());
				cloneSums.Add(i, 0);
			}
			
			// wraps enumerable with trivial select
			var wrappedEnumerable = clones[victimIdx].Select(a => a);
			ICloner<int> childCloner = new Cloner<int>(wrappedEnumerable);

			// creates clones of wrapped copy
			for (int i = 0; i < secondTryCount; i++)
			{
				int idx = shiftBase * (i + 1);
				clones.Add(idx, childCloner.GetClone());
				cloneSums.Add(idx, 0);
			}

			cloner.AllowReading();
			childCloner.AllowReading();

			// start sum calculation for all enumerables
			foreach (var kvp in clones)
			{
				// we should not read from base thread it is for internal use only
				if (kvp.Key != 0)
				{
					threads.Add(kvp.Key, new Thread((_n) =>
					{
						int n = (int)_n;
						var ienumerable = clones[n];
						int sum = 0;
						foreach (var t in ienumerable) sum += t;
						cloneSums[n] = sum;
					}));
					threads[kvp.Key].Start(kvp.Key);
				}
			}

			// wait
			bool b = false;
			do { Thread.Sleep(50); b = threads.Values.Any(t => t.IsAlive); } while (b);

			// asserts
			foreach (var kvp in clones)
				if (kvp.Key != victimIdx)
					Assert.AreEqual(expectedSum, cloneSums[kvp.Key]);
		}

		[TestMethod]
		public void FactoryTestPlain()
		{
			int cnt = 100;
			var ie = Enumerable.Range(1, cnt);
			var expected = (1 + cnt) * cnt / 2;
			var factory = new CloneFactory<int>(ie);
			var iestack = new Stack<IEnumerable<int>>();

			for (int i = 0; i < 2000; i++) iestack.Push(factory.GetClone());
			factory.AllowReading();

			var iEnums = iestack.ToDictionary(a => Guid.NewGuid(), a => a.GetEnumerator());
			var results = iEnums.Keys.ToDictionary(g => g, g => 0);

			int z = 0;
			bool goNext;
			do
			{
				goNext = false;

				foreach (var q in iEnums)
				{
					goNext |= q.Value.MoveNext();
					if (goNext)
						results[q.Key] += q.Value.Current;
					z = q.Value.Current;
				}
			} while (goNext);

			foreach (var kvp in results)
				Assert.AreEqual(expected, kvp.Value);
		}

		[TestMethod]
		public void FactoryTestParallel()
		{
			int length = 1000;
			var someEnumerable = Enumerable.Range(1, length);
			var expected = (1 + length) * length / 2;
			var factory = new CloneFactory<int>(someEnumerable);
			var iestack = new Stack<IEnumerable<int>>();

			for (int i = 0; i < 62; i++) iestack.Push(factory.GetClone());
			factory.AllowReading();

			var ies = iestack.ToDictionary(g => Guid.NewGuid(), g => g);
			var results = ies.Keys.ToDictionary(g => g, g => 0);

			var threads = results.Keys.ToDictionary(g => g,
				g => new Thread((_n) =>
				{
					Guid n = (Guid)(_n);
					int s = ies[n].Sum();
					results[n] = s;
				}));

			foreach (var t in threads) t.Value.Start(t.Key);
			do { Thread.Sleep(50); } while (threads.Values.Any(a => a.IsAlive));

			foreach (var kvp in results)
				Assert.AreEqual(expected, kvp.Value);
		}

		[TestMethod]
		public void TestEmpty()
		{
			int cnt = 0;
			var ie = Enumerable.Range(1, cnt);
			var expected = (1 + cnt) * cnt / 2;
			var factory = new CloneFactory<int>(ie);
			var iestack = new Stack<IEnumerable<int>>();

			for (int i = 0; i < 200; i++) iestack.Push(factory.GetClone());
			factory.AllowReading();

			var ies = iestack.ToDictionary(g => Guid.NewGuid(), g => g);
			var results = ies.Keys.ToDictionary(g => g, g => 0);
			var threads = results.Keys.ToDictionary(g => g,
				g => new Thread((_n) =>
				{
					Guid n = (Guid)(_n);
					int s = ies[n].Sum();
					results[n] = s;
				}));

			foreach (var t in threads) t.Value.Start(t.Key);
			do { Thread.Sleep(50); } while (threads.Values.Any(a => a.IsAlive));
			foreach (var kvp in results)
				Assert.AreEqual(expected, kvp.Value);
		}
	}
}
